/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package src;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

class Account {

    private int money;
    private final int id;

    Account(int id) {
        this.id = id;
    }

    int getId() {
        return id;
    }

    int getMoney() {
        return money;
    }

    void withdraw(int sum) {
        if (money < sum) {
            throw new IllegalArgumentException("Account sum must not be negative!");
        }
        money -= sum;
    }

    boolean canWithdraw(int sum) {
        if (money < sum) {
            return false;
        } else {
            return true;
        }
    }

    void deposit(int sum) {
        money += sum;
    }
}

class Bank {

    private int nextId = 0;
    private List<Account> accounts = new ArrayList<>();

    synchronized Account createNewAccount() {
        Account acc = new Account(nextId++);
        accounts.add(acc);
        return acc;
    }

    synchronized long getAllMoney() {
        long sum = 0;
        for (Account acc : accounts) {
            sum += acc.getMoney();
        }
        return sum;
    }

    void transfer(Account from, Account to, int amount) {
        Account first, second;
        if (from.getId() > to.getId()) {
            first = to;
            second = from;
        } else {
            first = from;
            second = to;
        }
        synchronized (first) {
            synchronized (second) {
                if (!from.canWithdraw(amount)) {
                    return;
                }
                from.withdraw(amount);
                to.deposit(amount);
            }
        }
    }

    Account getRandomAccount() {
        return accounts.get(new Random().nextInt(accounts.size()));
    }
}

/**
 *
 * @author Oksana_Moroz
 */
public class Pr5 {

    static class TestRunnable implements Runnable {

        Bank bank;

        public TestRunnable(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {
            Random random = new Random();
            int amount = Math.abs(random.nextInt(1000000000));
            Account from = bank.getRandomAccount();
            Account to = bank.getRandomAccount();
            bank.transfer(from, to, amount);
        }
    }

    static void testTranser(Bank bank) {
        for (int i = 0; i < 2000; i++) {
            Random random = new Random();
            int amount = Math.abs(random.nextInt(1000000000));
            Account from = bank.getRandomAccount();
            Account to = bank.getRandomAccount();
            bank.transfer(from, to, amount);
        }
    }

    public static void main(String[] args) {
        Random random = new Random();
        Bank bank = new Bank();
        for (int i = 0; i < 500; i++) {
            Account acc = bank.createNewAccount();
            int sum = Math.abs(random.nextInt(1000000000));
            acc.deposit(sum);
        }
        System.out.println("All money: " + bank.getAllMoney());
//        testTranser(bank);
        int numThreads = 2000;
        Thread[] threads = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(new TestRunnable(bank));
            threads[i].start();
        }
        for (int i = 0; i < numThreads; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException ex) {
                Logger.getLogger(Pr5.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        System.out.println("All money after transactions: " + bank.getAllMoney());
    }
}
